package com.lagou.mvc.framework;

import com.lagou.mvc.framework.annotation.RequestMapping;
import com.lagou.mvc.framework.annotation.Security;
import com.lagou.mvc.framework.pojo.BeanDefinition;
import com.lagou.mvc.framework.pojo.Handler;
import com.lagou.mvc.framework.util.BeanDefinitionUtil;
import com.lagou.mvc.framework.util.PackageUtil;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @ClassName DispatchServlet
 * @Description
 * @Author playboy
 * @Date 2021/4/29 4:23 下午
 * @Version 1.0
 **/
public class DispatchServlet extends HttpServlet {

	//扫描到的所有类
	List<Class<?>> classes = new ArrayList<>();

	//存储beanId和BeanDefinition之间关系
	Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

	List<Handler> handlerMappings = new ArrayList<>();

	@Override
	public void init(ServletConfig config) throws ServletException {
		//获取配置文件
		String contextLocaion = config.getInitParameter("contextLocaion");
		//解析xml文件
		InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextLocaion);
		SAXReader saxReader = null;
		try {
			Document read = new SAXReader().read(in);
			Element rootElement = read.getRootElement();
			List<Element> elements = rootElement.selectNodes("//component-scan");
			for (Element element : elements) {
				String basePackage = element.attributeValue("base-package");
				//扫描包
				doScanPackage(basePackage);
				//获取BeanDefinition
				doGetBeanDefinition();
				//根据BeanDefinition生成handler
				doCreateHandler();
			}
			System.out.println("init success");

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 扫描包，并将包中的.class放入到classes中
	 *
	 * @param basePackage
	 */
	private void doScanPackage(String basePackage) {
		PackageUtil.scan(classes, basePackage);
	}

	/**
	 * 根据classes中的数据解析class
	 */
	private void doGetBeanDefinition() {
		beanDefinitionMap = BeanDefinitionUtil.createBeanDefinition(classes);
	}

	/**
	 * 解析beanDefinitionMap
	 */
	private void doCreateHandler() throws InstantiationException, IllegalAccessException {
		for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
			BeanDefinition beanDefinition = entry.getValue();
			Class<?> clazz = beanDefinition.getClazz();
			//获取当前类中的所有方法
			Method[] declaredMethods = clazz.getDeclaredMethods();
			//当前的对象所有的handler公用
			Object o = clazz.newInstance();
			for (Method method : declaredMethods) {
				//解析每一个方法，一个方法生成一个handler
				if (method.isAnnotationPresent(RequestMapping.class)) {
					Handler handler = new Handler();
					handlerMappings.add(handler);
					handler.setObj(o);
					handler.setMethod(method);
					//处理请求路径
					RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
					String requestMappingValue = requestMapping.value();
					if (!requestMappingValue.equals("")) {
						handler.setPattern(Pattern.compile(beanDefinition.getBashUrl() + requestMappingValue));
					} else {
						continue;
					}
					//处理权限
					List<String> users = new ArrayList<>();
					if (method.isAnnotationPresent(Security.class)) {
						Security security = method.getAnnotation(Security.class);
						String[] securityValue = security.value();
						for (String s : securityValue) {
							users.add(s);
						}
					}
					users.addAll(beanDefinition.getUsers());
					handler.setUsers(users);
					//处理方法参数和位置
					//1.如果参数是HttpServletRequest和HttpServletResponse,则将名字直接定义为这两个值
					//2.如果是其他类型的参数，
					int parameterCount = method.getParameterCount();
					String[] args = new String[parameterCount];
					Parameter[] parameters = method.getParameters();
					for (int i = 0; i < parameters.length; i++) {
						Parameter parameter = parameters[i];
						Class<?> type = parameter.getType();
						if (type == HttpServletRequest.class) {
							args[i] = "HttpServletRequest";
						} else if (type == HttpServletResponse.class) {
							args[i] = "HttpServletResponse";
						} else {
							String name = parameter.getName();
							args[i] = name;
						}
					}
					handler.setArgs(args);
				}
			}
		}
	}


	//*****************解析mvc容器****************************


	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String requestURI = req.getRequestURI();
		resp.setCharacterEncoding("gbk");
		Handler handler = null;
		for (Handler handlerMapping : handlerMappings) {
			if (handlerMapping.getPattern().matcher(requestURI).matches()) {
				handler = handlerMapping;
			}
		}
		if (handler == null) {
			resp.getWriter().write("未找到匹配的url");
			return;
		}
		//没有用户权限判断类或者方法上是否有@Security注解注解，如果有的话，则直接认为没有访问权限
		boolean clazzSecurity = handler.getObj().getClass().isAnnotationPresent(Security.class);
		boolean methodSecurity = handler.getMethod().isAnnotationPresent(Security.class);
		if (clazzSecurity || methodSecurity) {
			//先判断是否有权限
			String username = req.getParameter("username");
			if (!handler.getUsers().contains(username)) {
				resp.getWriter().write("当前用户无访问权限");
				return;
			}
		}
		//整理参数
		String[] argNames = handler.getArgs();
		Object[] args = new Object[argNames.length];
		for (int i = 0; i < argNames.length; i++) {
			String argName = argNames[i];
			if (argName.equalsIgnoreCase("HttpServletRequest")) {
				args[i] = req;
			} else if (argName.equalsIgnoreCase("HttpServletResponse")) {
				args[i] = resp;
			} else {
				args[i] = req.getParameter(argName);
			}
		}
		//开始反射调用
		Method method = handler.getMethod();
		Object obj = handler.getObj();
		try {
			method.invoke(obj, args);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}

	}
}
